#pragma once

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>

#include "nocopy.hpp"
#include "log.hpp"
#include "epoller.hpp"
#include "socket.hpp"
#include "connection.hpp"
#include "setNonBlock.hpp"

// 使用et模式，提高效率
// et模式下，新事件到达时只会通知一次
const uint32_t EVENT_IN = (EPOLLIN | EPOLLET);
const uint32_t EVENT_OUT = (EPOLLOUT | EPOLLET);
const uint32_t EVENT_IN_AND_OUT = (EPOLLOUT | EPOLLIN | EPOLLET);
const int MAX_EVENT_NUM = 64;

using funcStr_t = std::function<std::string(std::string &)>;
class tcpServer : public nocopy, public std::enable_shared_from_this<tcpServer>
{
    const static int MAX_EVENT_NUM = 1024;

private:
    std::shared_ptr<tcp_socket> _listenSockPtr;
    std::shared_ptr<epoller> _epollModle;
    std::shared_ptr<connectContainer> _connectContainerPtr;
    struct epoll_event _events[MAX_EVENT_NUM];
    funcStr_t _funCb;
    uint16_t _port;
    bool _quit;

public:
    tcpServer(uint16_t port, funcStr_t bk)
        : _port(port), _quit(true), _funCb(bk) {}
    void init()
    {
        _listenSockPtr = std::make_shared<tcp_socket>();
        _epollModle = std::make_shared<epoller>();
        // shared_from_this()使用的前提是当前对象已经被shared_ptr管理了
        _connectContainerPtr = std::make_shared<connectContainer>(shared_from_this());

        _listenSockPtr->bind(_port);
        if (_listenSockPtr->listen() == -1)
        {
            lg(Error, "socket listen fail!");
            exit(2);
        }
        // 设置端口可重复使用
        _listenSockPtr->set_no_time_wait();
        // 将该套接字设置成非阻塞
        int fd = _listenSockPtr->fd();
        setNonBlock(fd);
        _epollModle->epollFileUpdate(EPOLL_CTL_ADD, fd, EVENT_IN);
        // 新建一个和listen_socket的链接管理器，绑定链接接收功能并加入connection中
        _connectContainerPtr->addConnection(fd,
                                            std::bind(&tcpServer::accepter, this, std::placeholders::_1), nullptr, nullptr);
    }
    void loop(int timeout)
    {
        _quit = false;
        while (!_quit)
        {
            dispatcher(timeout);
            _connectContainerPtr->printOnlineList();
        }
    }

private:
    void accepter(std::shared_ptr<connection> pcon)
    {
        int sock = pcon->getFd();
        // std::cout << sock << std::endl;
        while (true)
        {
            struct sockaddr_in src;
            socklen_t len = sizeof src;
            int fd = accept(sock, (sockaddr *)&src, &len);
            if (fd < 0)
                return;
            char cip[ip_size] = {0};
            std::string ip = inet_ntop(AF_INET, &(src.sin_addr), cip, sizeof cip - 1);
            uint16_t port = ntohs(src.sin_port);
            lg(Info, "get a new fd, fd:%d ", fd);
            // pcon->_conectContainer.
            // 添加存在链接
            _connectContainerPtr->addConnection(fd,
                                                std::bind(&tcpServer::recver, this, std::placeholders::_1),
                                                std::bind(&tcpServer::sender, this, std::placeholders::_1),
                                                std::bind(&tcpServer::excepter, this, std::placeholders::_1));
            lg(Info, "add connect done, fd: %d", fd);
            // 添加事件关注
            _epollModle->epollFileUpdate(EPOLL_CTL_ADD, fd, EVENT_IN);
            lg(Info, "add fd to epoller done, fd : %d", fd);
        }
    }
    void dispatcher(int timeout)
    {
        int n = _epollModle->epollWait(_events, MAX_EVENT_NUM, timeout);
        if (n > 0)
        {
            // 有事件到达，准备处理事件
            for (int i = 0; i < n; ++i)
            {
                int fd = _events[i].data.fd;
                uint32_t flag = _events[i].events;
                // std::cout << fd << std::endl;
                // 如果不是一个正确的文件描述符，直接跳过
                if (!_connectContainerPtr->isSafeFd(fd))
                    return;

                if (flag & EPOLLERR) // 将所有异常处理都转换成读处理
                    flag = EPOLLIN;
                if (flag & EPOLLHUP)
                    flag = EPOLLIN;
                // // 如果是读事件，进行事件派发
                if (flag & EPOLLIN)
                {
                    lg(Info, "fd: %d get a read event", fd);
                    _connectContainerPtr->at(fd)->read();
                }
                if (flag & EPOLLOUT)
                    _connectContainerPtr->at(fd)->write();
            }
        }
        else if (n == 0)
            lg(Info, "epoll wait timeout");
        else
            lg(Warning, "epoll wait fail!");
    }

    void recver(std::shared_ptr<connection> pcon)
    {
        char mesg[mesg_size];
        int fd = pcon->getFd();
        // std::cout << fd << "inbuffer: " ;
        ssize_t n = read(fd, mesg, sizeof mesg - 1);
        if (n > 0)
        {
            mesg[n] = 0;
            std::string &inbuf = pcon->inbuffer();
            inbuf += mesg;
            std::string resp = _funCb(inbuf);
            // 判断是不是有响应，没有响应就跳过
            if (resp.empty())
                return;
            // 将结果发送给对方
            n = write(fd, resp.c_str(), resp.size());
            if (n < 0)
            {
                lg(Warning, "write error, fd: %d", fd);
                excepter(pcon);
            }
            else if (n == 0)
            {
                lg(Info, "client close the link, server too, fd: %d", fd);
                excepter(pcon);
            }
            else if (n < resp.size()) // 如果没有发送完成
            {
                // 如果没有将全部信息成功发送，那就要添加写时间关心
                std::string &outbuf = pcon->outbuffer();
                outbuf += resp.substr(n);
                _epollModle->epollFileUpdate(EPOLL_CTL_MOD, fd, EVENT_IN_AND_OUT);
            }
            else
                lg(Info, "write success, fd: %d...", fd);
        }
        else if (n == 0)
        {
            // 客户端关闭链接，服务器也取消关闭
            lg(Info, "client close the link, fd: %d", fd);
            excepter(pcon);
        }
        else
        {
            lg(Warning, "read error, fd: %d");
            excepter(pcon);
        }
    }

    void sender(std::shared_ptr<connection> pcon)
    {
        int fd = pcon->getFd();
        std::string &outbuf = pcon->outbuffer();
        ssize_t n = write(fd, outbuf.c_str(), outbuf.size());
        if (n < 0)
        {
            lg(Warning, "write error, fd: %d", fd);
            excepter(pcon);
        }
        else if (n == 0)
        {
            lg(Info, "client close the link, server too, fd: %d", fd);
            excepter(pcon);
        }
        else if (n < outbuf.size())
            outbuf = outbuf.substr(n);
        else
        {
            lg(Info, "write success, fd: %d...", fd);
            _epollModle->epollFileUpdate(EPOLL_CTL_MOD, fd, EVENT_IN);
        }
    }

    void excepter(std::shared_ptr<connection> pcon)
    {
        // 异常处理，关闭连接
        int fd = pcon->getFd();
        _epollModle->epollFileDel(fd);
        if (!_connectContainerPtr->isSafeFd(fd))
        {
            lg(Warning, "this fd is not exists, fd: %d", fd);
            return;
        }
        _connectContainerPtr->delConnection(fd);
        close(fd);
    }
};
